package mbsl.ui;

import java.util.ArrayList;
import java.util.List;

import mbsl.util.SWTUtils;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;

/**
 * A custom Slider.
 * <p>
 * This one is represented as a bar, which follows the mouse when hovering over it.  Set a value by left-click, and unset by right-click.
 * In its disabled state, it becomes just a bar, separated from the enabled state by the lack of a border.
 */
public class Slider extends Canvas {
	public Slider(final Composite pParent, int pSteps, int pStyle) {
		this(pParent, pStyle);
		
		setSteps(pSteps);
	}
	public Slider(final Composite pParent, int pStyle) {
		super(pParent, pStyle);
		
		mBarCreator = new BarCreator(getDisplay(), getBounds().height, getBounds().width, 0, 5); // Setting an arbitrary step value here

		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				GC gc = event.gc;
				
				gc.setBackground(pParent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
					
				gc.fillRectangle(1, 1, getBounds().width - 2, getBounds().height - 2);

				if (isEnabled()) {
					gc.setForeground(pParent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BORDER));
					
					gc.drawRoundRectangle(0, 0, getBounds().width - 1, getBounds().height - 1, 2, 2);
				}				

				gc.drawImage(mBarCreator.getBar(mCurrentValue), 1, 1);
			}
		});
		addMouseMoveListener(new MouseMoveListener() {
			@Override
			public void mouseMove(MouseEvent e) {
				if (isEnabled())
					setFillGrade(e.x);
			}
		});
		addMouseListener(new MouseAdapter() {
			public void mouseUp(MouseEvent pE) {
				if (!isEnabled())
					return;
				
				if (pE.button == 1) // First MB
					setValue(mCurrentValue);
				else if (pE.button == 3) { // Third MB (not wheel)
					setValue(0);
					
					redraw();
				}
				
				notifySelectionListeners();
			}
		});
		addMouseTrackListener(new MouseTrackAdapter() {
			public void mouseExit(MouseEvent pE) {
				if (!isEnabled())
					return;
				
				mCurrentValue = mValue;
				
				redraw();
			}
		});
		addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent pE) {
				mBarCreator.setHeight(getBounds().height - 2);
				mBarCreator.setWidth(getBounds().width - 2);
			}
		});
	}
	
/*	public void setSize(int pWidth, int pHeight) {
		mBarCreator.setWidth(pWidth);
		mBarCreator.setHeight(pHeight);
		
		redraw();
		
		super.setSize(pWidth, pHeight);
	}
*/	
	public void setEnabled(boolean pEnable) {
		super.setEnabled(pEnable);
		
		redraw();
	}
	private void notifySelectionListeners() {
		if (mSelectionListeners == null || mSelectionListeners.isEmpty())
			return;
		
		Event event = new Event();
		
		event.widget = this;

		SelectionEvent selectionEvent = new SelectionEvent(event);
		
		for (SelectionListener listener : mSelectionListeners)
			listener.widgetSelected(selectionEvent);
	}
	/**
	 * Add a Selection Listener to the Control.  Listeners will be notified when the user sets or unsets a value.  The Event will always
	 * be sent to widgetSelected().
	 * @param pSelectionListener
	 */
	public void addSelectionListener(SelectionListener pSelectionListener) {
		if (mSelectionListeners == null)
			mSelectionListeners = new ArrayList<SelectionListener>();
		
		if (!mSelectionListeners.contains(pSelectionListener))
			mSelectionListeners.add(pSelectionListener);
	}
	/**
	 * Removes the passed SelectionListener from the Control.
	 */
	public void removeSelectionListener(SelectionListener pSelectionListener) {
		if (mSelectionListeners == null)
			return;
		
		mSelectionListeners.remove(pSelectionListener);
	}
	/**
	 * Returns the number of steps on this Slider.
	 * @return
	 */
	public int getSteps() {
		return mSteps;
	}
	public void setSteps(int pSteps) {
		if (pSteps < 1)
			mSteps = 1;
		else
			mSteps = pSteps;

		mBarCreator.setMaxRating(mSteps);
	}
	/**
	 * Returns the currently selected value.  Zero means no value is selected.
	 * @return
	 */
	public int getValue() {
		return mValue;
	}
	public void setValue(int pValue) {
		mValue = pValue;

		mCurrentValue = mValue;
	}
	
	private void setFillGrade(int pWidthCoordinate) {
		int usableWidth = (int)(getBounds().width - getBounds().width * FINAL_STEP_THRESHOLD);
		
		double blockSize = (double)(usableWidth) / mSteps;

		pWidthCoordinate += blockSize / 2;
		
		int newValue;
		
		if (pWidthCoordinate >= usableWidth)
			newValue = mSteps;
		else
			newValue = (int)((pWidthCoordinate - pWidthCoordinate * FINAL_STEP_THRESHOLD) / blockSize);
		
		if (newValue != mCurrentValue) {
			mCurrentValue = newValue > mSteps ? mSteps : newValue;
			redraw(0, 0, getBounds().width, getBounds().height, false);
		}
	}
	
	private int mValue;
	private int mSteps;
	private int mCurrentValue;
	private BarCreator mBarCreator;
	private List<SelectionListener> mSelectionListeners;
	
	public static void main(String[] pArgs) {
		Display display = new Display();
		
		Shell shell = new Shell(display);
		
		shell.setLayout(new GridLayout());
		
		final Slider s = new Slider(shell, 12, SWT.NONE);
		
		s.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		s.setValue(4);
		s.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent pE) {
				s.setEnabled(false);
			}
		});
		
		shell.setBounds(100, 100, 500, 100);
		shell.pack();
		shell.setVisible(true);
		
		SWTUtils.block(shell);
		
		display.dispose();
	}
	/** 
	 * At which point the bar should snap to the final step, in a percentage of its length.
	 * <p>
	 * If the bar is fewer pixels wide than it has steps, the last step will be unreachable if this is zero.
	 */
	private static final double FINAL_STEP_THRESHOLD = 0.01;
}